﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace GUITest
{
    public partial class Form1 : Form
    {

        public class BlockGenerator
        {
            const int BlOCK_WIDTH = 4;
            const int BLOCK_DEPTH = 4;
            static Random rand = new Random();

            private class ConnPoint
            {
                public bool IsConnected;
                static Random rand = new Random();

                public void Random()
                {
                    this.IsConnected = (rand.NextDouble() < 0.25);
                }
            }

            private class ConnectionSet
            {
                public ConnPoint Top;
                public ConnPoint Bottom;
                public ConnPoint Left;
                public ConnPoint Right;

                static Random rand = new Random();

                public ConnectionSet()
                {
                    Top = new ConnPoint();
                    Bottom = new ConnPoint();
                    Left = new ConnPoint();
                    Right = new ConnPoint();
                }

                public ConnectionSet(ConnectionSet connectionSet, int side)
                    : this()
                {
                    if (side == 0)
                    {
                        this.Bottom = connectionSet.Top;
                    }
                    else
                    {
                        this.Left = connectionSet.Right;
                    }
                }

                public ConnectionSet(ConnectionSet connectionSet1, ConnectionSet connectionSet2)
                    : this()
                {
                    this.Left = connectionSet1.Right;
                    this.Bottom = connectionSet2.Top;
                }

                public static ConnectionSet[,] GenerateBlock(int width, int depth)
                {
                    ConnectionSet[,] set = new ConnectionSet[width - 1, depth - 1];

                    for (int y = 0; y < set.GetLength(1); y++)
                    {
                        for (int x = 0; x < set.GetLength(0); x++)
                        {
                            if (x == 0 && y == 0)
                            {
                                set[x, y] = new ConnectionSet();
                            }
                            else if (x == 0)
                            {
                                set[x, y] = new ConnectionSet(set[x, y - 1], 0);
                            }
                            else if (y == 0)
                            {
                                set[x, y] = new ConnectionSet(set[x - 1, y], 1);
                            }
                            else
                            {
                                set[x, y] = new ConnectionSet(set[x - 1, y], set[x, y - 1]);
                            }
                            set[x, y].Top.Random();
                            set[x, y].Bottom.Random();
                            set[x, y].Left.Random();
                            set[x, y].Right.Random();
                        }

                    }

                    FixLs(set);
                    ExposeCenter(set);
                    FixLs(set);

                    Console.WriteLine(GetString(set));
                    return set;
                }

                private static void ExposeCenter(ConnectionSet[,] set)
                {
                    int width = set.GetLength(0);
                    int depth = set.GetLength(1);
                    for (int x = 1; x <= (width / 2); x++)
                    {
                        for (int y = 1; y <= (depth / 2); y++)
                        {
                            switch (set[x, y].GetCount())
                            {
                                case 0:
                                    Expose(set[x - 1, y - 1].Top, set[x - 1, y - 1].Right);
                                    Expose(set[x - 1, y + 1].Bottom, set[x - 1, y + 1].Right);
                                    Expose(set[x + 1, y - 1].Top, set[x + 1, y - 1].Left);
                                    Expose(set[x + 1, y + 1].Bottom, set[x + 1, y + 1].Left);
                                    break;
                                case 1:
                                    ExposeTri(set, x, y);
                                    break;
                                case 2:
                                    ExposeBi(set, x, y);
                                    break;
                                case 4:
                                    Expose(set, x, y);
                                    break;
                            }
                        }
                    }

                }

                private static void ExposeTri(ConnectionSet[,] set, int x, int y)
                {
                    //the divide is horizontal... -|-
                    if (set[x, y].Top.IsConnected)
                    {

                        Expose(set[x - 1, y - 1].Top, set[x - 1, y - 1].Right);
                        Expose(set[x + 1, y - 1].Top, set[x + 1, y - 1].Left);

                        if (
                            !(set[x - 1, y + 1].Bottom.IsConnected && set[x - 1, y + 1].Right.IsConnected &&
                            set[x + 1, y + 1].Bottom.IsConnected && set[x + 1, y + 1].Left.IsConnected))
                        {
                            switch (rand.Next(2))
                            {
                                case 0:
                                    Expose(set[x - 1, y + 1].Bottom, set[x - 1, y + 1].Right);
                                    break;
                                case 1:
                                    Expose(set[x + 1, y + 1].Bottom, set[x + 1, y + 1].Left);
                                    break;
                            }
                        }

                    }
                    else if (set[x, y].Bottom.IsConnected)// _|_
                    {
                        //expose top left and right
                        Expose(set[x - 1, y + 1].Bottom, set[x - 1, y + 1].Right);
                        Expose(set[x + 1, y + 1].Bottom, set[x + 1, y + 1].Left);
                        //expose bottom
                        if (
                            !(set[x - 1, y - 1].Top.IsConnected && set[x - 1, y - 1].Right.IsConnected &&
                            set[x + 1, y - 1].Top.IsConnected && set[x + 1, y - 1].Left.IsConnected))
                        {
                            switch (rand.Next(2))
                            {
                                case 0:
                                    Expose(set[x - 1, y - 1].Top, set[x - 1, y - 1].Right);
                                    break;
                                case 1:
                                    Expose(set[x + 1, y - 1].Top, set[x + 1, y - 1].Left);
                                    break;
                            }
                        }
                    }
                    else if (set[x, y].Left.IsConnected)// |-
                    {
                        Expose(set[x + 1, y + 1].Bottom, set[x + 1, y + 1].Left);
                        Expose(set[x + 1, y - 1].Top, set[x + 1, y - 1].Left);

                        if (
                            !(set[x - 1, y + 1].Bottom.IsConnected && set[x - 1, y + 1].Right.IsConnected &&
                            set[x + 1, y + 1].Bottom.IsConnected && set[x + 1, y + 1].Left.IsConnected))
                        {
                            switch (rand.Next(2))
                            {
                                case 0:
                                    Expose(set[x - 1, y + 1].Bottom, set[x - 1, y + 1].Right);
                                    break;
                                case 1:
                                    Expose(set[x - 1, y - 1].Top, set[x - 1, y - 1].Right);
                                    break;
                            }
                        }

                    }
                    else if (set[x, y].Right.IsConnected)// -|
                    {
                        Expose(set[x - 1, y + 1].Bottom, set[x - 1, y + 1].Right);
                        Expose(set[x - 1, y - 1].Top, set[x - 1, y - 1].Right);

                        if (
                            !(set[x + 1, y + 1].Bottom.IsConnected && set[x + 1, y + 1].Left.IsConnected &&
                            set[x + 1, y - 1].Top.IsConnected && set[x + 1, y - 1].Left.IsConnected))
                        {
                            switch (rand.Next(2))
                            {
                                case 0:
                                    Expose(set[x + 1, y + 1].Bottom, set[x + 1, y + 1].Left);
                                    break;
                                case 1:
                                    Expose(set[x + 1, y - 1].Top, set[x + 1, y - 1].Left);
                                    break;
                            }
                        }
                    }
                }

                private static void ExposeBi(ConnectionSet[,] set, int x, int y)
                {
                    //the divide is horizontal...
                    if (set[x, y].Top.IsConnected)
                    {
                        if (
                            !(set[x - 1, y + 1].Bottom.IsConnected && set[x - 1, y + 1].Right.IsConnected &&
                            set[x + 1, y + 1].Bottom.IsConnected && set[x + 1, y + 1].Left.IsConnected))
                        {
                            switch (rand.Next(2))
                            {
                                case 0:
                                    Expose(set[x - 1, y + 1].Bottom, set[x - 1, y + 1].Right);
                                    break;
                                case 1:
                                    Expose(set[x + 1, y + 1].Bottom, set[x + 1, y + 1].Left);
                                    break;
                            }
                        }
                        if (
                            !(set[x - 1, y - 1].Top.IsConnected && set[x - 1, y - 1].Right.IsConnected &&
                            set[x + 1, y - 1].Top.IsConnected && set[x + 1, y - 1].Left.IsConnected))
                        {
                            switch (rand.Next(2))
                            {
                                case 0:
                                    Expose(set[x - 1, y - 1].Top, set[x - 1, y - 1].Right);
                                    break;
                                case 1:
                                    Expose(set[x + 1, y - 1].Top, set[x + 1, y - 1].Left);
                                    break;
                            }
                        }
                    }
                    else // vertical divide
                    {
                        if (
                            !(set[x - 1, y + 1].Bottom.IsConnected && set[x - 1, y + 1].Right.IsConnected &&
                            set[x + 1, y + 1].Bottom.IsConnected && set[x + 1, y + 1].Left.IsConnected))
                        {
                            switch (rand.Next(2))
                            {
                                case 0:
                                    Expose(set[x - 1, y + 1].Bottom, set[x - 1, y + 1].Right);
                                    break;
                                case 1:
                                    Expose(set[x - 1, y - 1].Top, set[x - 1, y - 1].Right);
                                    break;
                            }
                        }
                        if (
                            !(set[x + 1, y + 1].Bottom.IsConnected && set[x + 1, y + 1].Left.IsConnected &&
                            set[x + 1, y - 1].Top.IsConnected && set[x + 1, y - 1].Left.IsConnected))
                        {
                            switch (rand.Next(2))
                            {
                                case 0:
                                    Expose(set[x + 1, y + 1].Bottom, set[x + 1, y + 1].Left);
                                    break;
                                case 1:
                                    Expose(set[x + 1, y - 1].Top, set[x + 1, y - 1].Left);
                                    break;
                            }
                        }
                    }
                }

                private static void Expose(ConnectionSet[,] set, int x, int y)
                {
                    if (!(set[x - 1, y - 1].Top.IsConnected && set[x - 1, y - 1].Right.IsConnected &&
                        set[x - 1, y + 1].Bottom.IsConnected && set[x - 1, y + 1].Right.IsConnected &&
                        set[x + 1, y - 1].Top.IsConnected && set[x + 1, y - 1].Left.IsConnected &&
                        set[x + 1, y + 1].Bottom.IsConnected && set[x + 1, y + 1].Left.IsConnected))
                    {
                        switch (rand.Next(4))
                        {
                            case 0:
                                Expose(set[x - 1, y - 1].Top, set[x - 1, y - 1].Right);
                                break;
                            case 1:
                                Expose(set[x - 1, y + 1].Bottom, set[x - 1, y + 1].Right);
                                break;
                            case 2:
                                Expose(set[x + 1, y - 1].Top, set[x + 1, y - 1].Left);
                                break;
                            case 3:
                                Expose(set[x + 1, y + 1].Bottom, set[x + 1, y + 1].Left);
                                break;
                        }
                    }
                }

                private static void Expose(ConnPoint connPoint1, ConnPoint connPoint2)
                {
                    if (!connPoint1.IsConnected && !connPoint2.IsConnected)
                    {
                        if (rand.Next(2) == 0)
                        {
                            connPoint1.IsConnected = true;
                        }
                        else
                        {
                            connPoint2.IsConnected = true;
                        }
                    }
                }

                private static void RecurseFix(ConnectionSet[,] set, int x, int y)
                {
                    if (set[x, y].FixLs())
                    {
                        if (x > 0)
                        {
                            RecurseFix(set, x - 1, y);
                        }
                        if (y > 0)
                        {
                            RecurseFix(set, x, y - 1);
                        }
                        if (x + 1 < set.GetLength(0))
                        {
                            RecurseFix(set, x + 1, y);
                        }
                        if (y + 1 < set.GetLength(1))
                        {
                            RecurseFix(set, x, y + 1);
                        }
                    }
                }

                private static void FixLs(ConnectionSet[,] set)
                {

                    for (int y = 0; y < set.GetLength(1); y++)
                    {
                        for (int x = 0; x < set.GetLength(0); x++)
                        {
                            RecurseFix(set, x, y);
                        }
                    }

                }

                private bool FixLs()
                {
                    if (CreatesCorner() || NonsensicalDevision())
                    {
                        this.Top.IsConnected = true;
                        this.Bottom.IsConnected = true;
                        this.Left.IsConnected = true;
                        this.Right.IsConnected = true;
                        return true;
                    }
                    return false;
                }

                private int GetCount()
                {
                    int count = 0;
                    if (this.Top.IsConnected)
                    {
                        count++;
                    }
                    if (this.Bottom.IsConnected)
                    {
                        count++;
                    }
                    if (this.Left.IsConnected)
                    {
                        count++;
                    }
                    if (this.Right.IsConnected)
                    {
                        count++;
                    }
                    return count;
                }

                private bool NonsensicalDevision()
                {
                    int count = GetCount();
                    if (count == 3)
                    {
                        return true;
                    }
                    return false;
                }

                private bool CreatesCorner()
                {
                    if (this.Top.IsConnected && this.Left.IsConnected && !(this.Bottom.IsConnected || this.Right.IsConnected))
                    {
                        return true;
                    }
                    if (this.Bottom.IsConnected && this.Left.IsConnected && !(this.Top.IsConnected || this.Right.IsConnected))
                    {
                        return true;
                    }
                    if (this.Top.IsConnected && this.Right.IsConnected && !(this.Bottom.IsConnected || this.Left.IsConnected))
                    {
                        return true;
                    }
                    if (this.Bottom.IsConnected && this.Right.IsConnected && !(this.Top.IsConnected || this.Left.IsConnected))
                    {
                        return true;
                    }
                    return false;
                }

                public static String GetString(ConnectionSet[,] set)
                {
                    String ret = "";
                    string bottom = "|";
                    for (int y = set.GetLength(1) - 1; y >= 0; y--)
                    {
                        ret += "|";

                        for (int x = 0; x < set.GetLength(0); x++)
                        {

                            if (x == 0 && !set[x, y].Left.IsConnected)
                            {
                                ret += "_";
                            }
                            else if (x == 0)
                            {
                                ret += " ";
                            }
                            if (!set[x, y].Top.IsConnected)
                            {
                                ret += "|";
                            }
                            else
                            {
                                ret += " ";
                            }
                            if (!set[x, y].Right.IsConnected)
                            {
                                ret += "_";
                            }
                            else
                            {
                                ret += " ";
                            }
                            if (y == 0 && !set[x, y].Bottom.IsConnected)
                            {
                                bottom += " |";
                            }
                            else if (y == 0)
                            {
                                bottom += "  ";
                            }

                        }
                        ret += "|\r\n";
                    }
                    return ret + bottom + " |";
                }
            }

            public class Vector2
            {
                public float x;
                public float y;
            }

            private static void SetBlocks(Vector2[, ,] blockSize)
            {
                float xRemaining = 1.0f;
                float yRemaining = 1.0f;

                float xmin = 1.0f / (float)(blockSize.GetLength(0) + 1);
                float ymin = 1.0f / (float)(blockSize.GetLength(1) + 1);
                float xstd = 1.0f / (float)(blockSize.GetLength(0) - 1);
                float ystd = 1.0f / (float)(blockSize.GetLength(1) - 1);

                //            Console.WriteLine("Xmin: " + xmin);
                //            Console.WriteLine("XMax: " + (xRemaining - (xmin * (blockSize.GetLength(0)-1))));
                //            Console.WriteLine("Ymin: " + ymin);

                float totalWidth = 0.0f;
                float totalDepth = 0.0f;

                for (int x = 0; x < blockSize.GetLength(0); x++)
                {
                    for (int y = 0; y < blockSize.GetLength(1); y++)
                    {
                        blockSize[x, y, 0] = new Vector2();
                    }
                }

                for (int x = 0, xLeft = blockSize.GetLength(0); x < blockSize.GetLength(0); x++, xLeft--)
                {
                    //                Console.WriteLine("Max: " + (xRemaining - (xmin * (float)(xLeft + 1))));
                    float xmax = Math.Min((xRemaining - (xmin * (float)(xLeft))), xstd - xmin);
                    float widthp = xmin + (float)(rand.NextDouble() * xmax);
                    xRemaining -= widthp;
                    totalWidth += widthp;
                    blockSize[x, 0, 0].x = widthp;
                    //                Console.WriteLine("X"+x+": "+widthp);
                }
                //            Console.WriteLine("Width: " + totalWidth);
                for (int y = 0, yleft = blockSize.GetLength(0); y < blockSize.GetLength(0); y++, yleft--)
                {
                    float ymax = Math.Min((yRemaining - (ymin * (float)(yleft))), ystd - ymin);
                    float depthp = ymin + (float)(rand.NextDouble() * ymax);
                    yRemaining -= depthp;
                    totalDepth += depthp;
                    blockSize[0, y, 0].y = depthp;
                    //                Console.WriteLine("Y" + y + ": " + depthp);
                }
                //            Console.WriteLine("Depth: " + totalDepth);
                for (int x = 0; x < blockSize.GetLength(0); x++)
                {
                    for (int y = 0; y < blockSize.GetLength(1); y++)
                    {
                        
                        blockSize[x, y, 0].x = blockSize[x, 0, 0].x;
                        blockSize[x, y, 0].y = blockSize[0, y, 0].y;
                    }
                }
                
                
            }

            private static void SetPlaces(Vector2[, ,] blockSize)
            {
                float width = 0.0f;
                float depth = 0.0f;

                for (int x = 0; x < blockSize.GetLength(0); x++)
                {
                    width += blockSize[x, 0, 0].x;
                }
                for (int y = 0; y < blockSize.GetLength(1); y++)
                {
                    depth += blockSize[0, y, 0].y;
                }
                Console.WriteLine("width: " + width);
                Console.WriteLine("depth: " + depth);

                float leftoverWidth = 1.0f - width;
                float leftoverDepth = 1.0f - depth;

                float widthSeperation = leftoverWidth / (float)(blockSize.GetLength(0) + 1);
                float depthSeperation = leftoverDepth / (float)(blockSize.GetLength(1) + 1);

                float accWidth = widthSeperation;
                float accDepth = depthSeperation;

                for (int x = 0; x < blockSize.GetLength(0); x++)
                {
                    for (int y = 0; y < blockSize.GetLength(1); y++)
                    {
                        blockSize[x, y, 1] = new Vector2();
                    }
                }

                for (int x = 0; x < blockSize.GetLength(0); x++)
                {
                    for (int y = 0; y < blockSize.GetLength(1); y++)
                    {
                        blockSize[x, y, 1].x = accWidth;
                    }
                    accWidth += blockSize[x, 0, 0].x + widthSeperation;
                }

                for (int y = 0; y < blockSize.GetLength(1); y++)
                {
                    for (int x = 0; x < blockSize.GetLength(0); x++)
                    {
                        blockSize[x, y, 1].y = accDepth;
                    }
                    accDepth += blockSize[0, y, 0].y + depthSeperation;
                }

            }

            private static void ApplyDimensions(Vector2[, ,] footprints, float width, float depth)
            {
                for (int x = 0; x < footprints.GetLength(0); x++)
                {
                    for (int y = 0; y < footprints.GetLength(1); y++)
                    {
                        footprints[x, y, 0].x *= width;
                        footprints[x, y, 0].y *= depth;
                        footprints[x, y, 1].x *= width;
                        footprints[x, y, 1].y *= depth;
                        footprints[x, y, 0].x = ((int)footprints[x, y, 0].x / 5) * 5;
                        footprints[x, y, 0].y = ((int)footprints[x, y, 0].y / 5) * 5;
                    }
                }
            }

            private static void MergeBlocks(Vector2[, ,] footprints, ConnectionSet[,] ConBlockSet)
            {
                for (int x = 0; x < ConBlockSet.GetLength(0); x++)
                {
                    for (int y = 0; y < ConBlockSet.GetLength(1); y++)
                    {
                        if (ConBlockSet[x, y].Bottom.IsConnected)
                        {
                            footprints[x + 1, y, 0].x = footprints[x, y, 0].x + footprints[x + 1, y, 0].x;
                            footprints[x + 1, y, 1].x = footprints[x, y, 1].x + (((footprints[x + 1, y, 1].x - footprints[x, y, 1].x) - footprints[x, y, 0].x)/2.0f);
                        }
                        if (ConBlockSet[x, y].Left.IsConnected)
                        {
                            footprints[x, y + 1, 0].y = footprints[x, y, 0].y + footprints[x, y + 1, 0].y;
                            footprints[x, y + 1, 1].y = footprints[x, y, 1].y + (((footprints[x, y + 1, 1].y - footprints[x, y, 1].y) - footprints[x, y, 0].y) / 2.0f);
                        }
                        if (x == (ConBlockSet.GetLength(1) - 1) && ConBlockSet[x, y].Right.IsConnected)
                        {
                            footprints[x + 1, y + 1, 0].y = footprints[x + 1, y, 0].y + footprints[x + 1, y + 1, 0].y;
                            footprints[x + 1, y + 1, 1].y = footprints[x + 1, y, 1].y + (((footprints[x + 1, y + 1, 1].y - footprints[x + 1, y, 1].y) - footprints[x + 1, y, 0].y) / 2.0f);
                        }
                        if (y == (ConBlockSet.GetLength(0) - 1) && ConBlockSet[x, y].Top.IsConnected)
                        {
                            footprints[x + 1, y + 1, 0].x = footprints[x, y + 1, 0].x + footprints[x + 1, y + 1, 0].x;
                            footprints[x + 1, y + 1, 1].x = footprints[x, y + 1, 1].x + (((footprints[x + 1, y + 1, 1].x - footprints[x, y + 1, 1].x) - footprints[x, y + 1, 0].x) / 2.0f);
                        }
                                                
                        if (ConBlockSet[x, y].Bottom.IsConnected || ConBlockSet[x, y].Left.IsConnected)
                        {
                            footprints[x, y, 0] = null;
                        }
                        if (x == (ConBlockSet.GetLength(1) - 1) && ConBlockSet[x, y].Right.IsConnected)
                        {
                            footprints[x + 1, y, 0] = null;
                        }
                        if (y == (ConBlockSet.GetLength(0) - 1) && ConBlockSet[x, y].Top.IsConnected)
                        {
                            footprints[x, y + 1, 0] = null;
                        }
                        
                    }
                }
            }

            public static Vector2[, ,] GenerateBuildingFootprints(float width, float depth)
            {


                Vector2[, ,] footprints = new Vector2[BlOCK_WIDTH, BLOCK_DEPTH, 2];

                ConnectionSet[,] ConBlockSet = ConnectionSet.GenerateBlock(BlOCK_WIDTH, BLOCK_DEPTH);

                SetBlocks(footprints);
                SetPlaces(footprints);
                ApplyDimensions(footprints, width, depth);
                MergeBlocks(footprints, ConBlockSet);

                return footprints;
            }         

        }

        BlockGenerator.Vector2[, ,] Block;

        public Form1()
        {
            InitializeComponent();
            Block = BlockGenerator.GenerateBuildingFootprints(500.0f, 500.0f);
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Pen redPen = new Pen(Color.Red, 3);

            // Create rectangle.
            Rectangle rect = new Rectangle(0,0,500,500);
            
            // Draw rectangle to screen.
            e.Graphics.DrawRectangle(redPen, rect);

            if (Block != null)
            {

                for (int x = 0; x < Block.GetLength(0); x++)
                {
                    for (int y = 0; y < Block.GetLength(1); y++)
                    {
                        if (Block[x, y, 0] != null)
                        {

                            Pen blackPen = new Pen(Color.Black, 3);

                            // Create rectangle.
                            rect = new Rectangle((int)Block[x, y, 1].x, (int)Block[x, y, 1].y, (int)Block[x, y, 0].x, (int)Block[x, y, 0].y);
                            Console.WriteLine("x: " + (int)Block[x, y, 1].x +
                                                " y: " + (int)Block[x, y, 1].y +
                                                " width: " + (int)Block[x, y, 0].x +
                                                " width: " + (int)Block[x, y, 0].y);
                            // Draw rectangle to screen.
                            e.Graphics.DrawRectangle(blackPen, rect);
                        }
                    }
                }
            }
        }

        private void Form1_KeyPress(object sender, KeyPressEventArgs e)
        {
            Block = BlockGenerator.GenerateBuildingFootprints(500.0f, 500.0f);
            this.Refresh();
        }
    }
}
